//==============================================================================
// Copyright (c) 2008-2013 Niall Ryan. All Rights Reserved.
//==============================================================================

#include <gtest/gtest.h>

#include "hydra/exchangers/LockFreeExchanger.h"
#include "hydra/thread/Thread.h"

using namespace Hydra;

template<typename Exchanger>
class ExchangerTest : public ::testing::Test
{
protected:
	static const int NUM_THREADS = 2;
	static const int NUM_ITERATIONS = 1000000;
	static void Exchange(ExchangerTest* test, int threadId)
	{
		for( int i=0; i<NUM_ITERATIONS; ++i )
		{
			int result;
			while( !test->m_exchanger.exchange(i*NUM_THREADS+threadId, &result) )
			{
			}
			++test->m_counters[i&(NUM_THREADS-1)];
		}
	}

	static void Offer(ExchangerTest* test)
	{
		for( int i=0; i<NUM_ITERATIONS; ++i )
		{
			while( !test->m_exchanger.offer(i) )
			{
			}
		}
	}
	static void Accept(ExchangerTest* test)
	{
		for( int i=0; i<NUM_ITERATIONS; ++i )
		{
			int result;
			while( !test->m_exchanger.accept(&result) )
			{
			}
			if( result == i )
				++test->m_counters[0];
		}
	}

	Exchanger m_exchanger;
	Atomic<int> m_counters[NUM_THREADS];
};

typedef ::testing::Types<LockFreeExchanger<int> > ExchangerTestTypes;
TYPED_TEST_CASE(ExchangerTest, ExchangerTestTypes);

TYPED_TEST(ExchangerTest, Stuff)
{
	//
	//single threaded functionality tests
	//
	int value;
	EXPECT_FALSE(m_exchanger.exchange(10, &value));

	//
	//multithread tests
	//
	Thread threads[NUM_THREADS];
	{
		for( int i=0; i<NUM_THREADS; ++i )
		{
			threads[i] = Thread(&Exchange, this, i);
			m_counters[i] = 0;
		}
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Start();
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Join();
		for( int i=0; i<NUM_THREADS; ++i )
			EXPECT_EQ(NUM_ITERATIONS, m_counters[i]);
	}
	{
		threads[0] = Thread(&Offer, this);
		threads[1] = Thread(&Accept, this);
		m_counters[0] = 0;
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Start();
		for( int i=0; i<NUM_THREADS; ++i )
			threads[i].Join();
		EXPECT_EQ(NUM_ITERATIONS, m_counters[0]);
	}
}
